背景
有一次,发现调用一个未声明的方法的时候,Xcode 居然没有像往常一样给出错误提示,而只是给了 Warning,研究后发现原来该文件不是 ARC,所以出现这种问题,那么 ARC 和 MRC 之间,对于调用未声明的方法,有什么区别呢?
实验
[self noSuchMethod];
MRC 下,调用一个未声明的方法,编译器会给出 Warning:
“Instance method ‘noSuchMethod’ not found (return type defaults to ‘id’)”
ARC 下,调用一个未声明的方法,编译器却给出 Error:
“No visible @interface for ‘xxx’ declares the selector ‘noSuchMethod’”
原因:
ARC 下,编译器需要知道方法返回值的所有者,才能正确在合适的地方添加 retain/release 等,所以显式调用一个未声明的方法在 ARC 下默认是不允许的
下面是具体的代码提示,跟Xcode的版本有关系,我的Xcode是Version 7.3.1 (7D1014)
MRC:
1 | id classA; |
ARC:
1 | id classA; |
问题:
@selector 和 NSSelectorFromString 区别
可以看到使用 @selector(),编译器可以知道相应方法没有被声明;而使用 NSSelectorFromString,编译器并不知情,因为该方法是动态的,在 Runtime 的时候才能确定相应的方法实现,所以编译器选择了忽略
所以 MRC 下很简单,@selector 就给警告, NSSelectorFromString 就不管
ARC 下,就复杂点,@selectot 依然给警告,但是 NSSelectorFromString 的处理就复杂点,继续往下看
那为什么 ARC 下,
performSelector:NSSelectorFromString()
会有 Leak Warning事实上,无论 xyz 方法存在不存在,只要是 ARC 下,使用
performSelector:NSSelectorFromString()
就会有该 Warning 产生,因为编译器并不知道你调用了什么方法(是含有alloc
/new
/copy
/mutableCopy
关键字的方法还是普通方法),那它也不知道该不该添加 retain/release 等,所以给出可能产生内存泄露的警告如果确定了这样调用没有内存问题,那么可以通过以下方法消除 Warning:
针对部分代码
1
2
3
4
[classA performSelector:NSSelectorFromString(@"xyz")];更骚一点的做法是
1
2
3
4
5
6
7
8
9
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
expr; \
_Pragma("clang diagnostic pop") \
} while(0)
SILENCE_PERFORMSELECTOR([classA performSelector:NSSelectorFromString(@"xyz")]);针对单个文件,与设置某个文件为非 ARC 类似(见小Tips),添加
-Wno-arc-performSelector-leaks
- 针对整个工程,Build Settings,搜索 Other Warning Flags,添加
-Wno-arc-performSelector-leaks
最后的问题,为什么 ARC 下,
performSelector:NSSelectorFromString() withObject:afterDelay:
就没有 Leak Warning 呢看看这几个函数的原型
1
2
3- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;可以看到,
performSelector:withObject:afterDelay:
返回值是 void。所以可以推测,Xcode 认为,你既然写了 afterDelay(即使是延迟0秒),那么它的返回值是 void,无论 selector 有没有返回值,都不需要为之添加 retain/release,所以这种情况下没有内存问题
小Tips
- ARC 与 MRC 互转:工程 -> Targets -> Build Phases -> Compile Sources -> 对应的.m文件的Compiler Flags添加
-fno-objc-arc
(MRC)/-fobjc-arc
(ARC) - 判断 ARC 与 MRC 的快速方法,在
dealloc
里面调用[super dealloc];
,如果报错则是 ARC,否则是 MRC